import time
from dataclasses import asdict

from common.exchanges import Exchange
from common.exchanges.binance import BinanceAccountType, BinancePosition, BinanceOrder
from common.exchanges.contants import ContratType, STABLE_COINS
from common.exchanges.entities import ApiAuthInfo, IndexPair
from common.exchanges.manager import ExchangeServiceManager
from common.utils.encrypt import AESEncryptor
from web import settings
from web.common.contants import RiskState
from web.service.base import ServiceBase
from web.settings import SUPPORT_BASE_CURRENCIES, SUPPORT_MARGIN_CURRENCIES


class AccountService(ServiceBase):

    CURRENCY_CONTRACT_MULTI = {
    }

    @classmethod
    def get_contract_multi(cls, exchange, currency):
        if exchange == Exchange.DERIBIT:
            return 1
        if currency == 'BTC':
            return 100
        else:
            return 10

    @classmethod
    def account2auth_info(cls, account):
        api_key = AESEncryptor.decode(account.api_key, settings.SECRET)
        api_secret = AESEncryptor.decode(account.api_secret, settings.SECRET)
        return ApiAuthInfo(api_key=api_key, api_secret=api_secret)

    @classmethod
    def default_account_data(cls, account):
        return {
            'exchange': account.exchange,
            'equity': 0,
            'expire': 0,
            'open': 0,
            'mm': 0,
            'positions': [],
            'open_orders': [],
            'risk_items': [],
            'id': account.id,
        }

    def get_account_summary(self, account,):
        try:
            s = time.time()
            account_data = self.default_account_data(account)
            auth_info = self.account2auth_info(account)
            service = ExchangeServiceManager.pri_factory(account.exchange, auth_info)
            # service.check_auth()
            # service.ensure_catcher_run()

            if account.exchange == Exchange.BINANCE:
                account_types = [BinanceAccountType.Spot, BinanceAccountType.C_Future, BinanceAccountType.U_Future]
                summaries = service.account_summaries(
                    account_types=account_types
                )
                self._logger.info(f'get binance account summary {summaries}')
            elif account.exchange == Exchange.DERIBIT:
                summaries = service.account_summaries()
            else:
                return account_data

            open_orders = service.open_orders()
            positions = service.positions(kinds=[ContratType.Future])
            self._logger.info(f'{account.id} query account summary 1 token {time.time() - s}')
            pub_service = ExchangeServiceManager.pub_factory(account.exchange)
            now_ts = int(time.time() * 1000)

            curr_position_map = {}
            index_price_map = {}

            for summary in summaries:
                if summary.equity <= 0:
                    continue
                if summary.margin_currency not in SUPPORT_MARGIN_CURRENCIES:
                    continue
                if summary.margin_currency in STABLE_COINS:
                    account_data['equity'] += summary.equity
                elif summary.margin_currency in SUPPORT_BASE_CURRENCIES:
                    index_price = index_price_map.get(summary.margin_currency)
                    if index_price is None:
                        self._logger.info(f'get index price for {summary.margin_currency}USD')
                        index_price = pub_service.index_price(IndexPair(summary.margin_currency, 'USD')).price
                        index_price_map[summary.margin_currency] = index_price

                    account_data['equity'] += summary.equity * index_price
                    curr_position = curr_position_map.setdefault(summary.margin_currency, {
                        'currency': summary.margin_currency,
                        'spot': 0,
                        'delta': 0,
                        'details': []
                    })

                    curr_position['delta'] += summary.equity
                    curr_position['spot'] += summary.equity
                if summary.mm_ratio and summary.mm_ratio > 0.05:
                    # account_data['risk_state'] = RiskState.AtRisk
                    account_data['risk_items'].append({
                        'name': f'{summary.margin_currency} MM',
                        'value': f'{round(summary.mm_ratio * 100, 2)}%',
                        'level': -1
                    })
                if summary.is_cross:
                    account_data['mm'] = round(max(account_data['mm'], summary.mm_ratio * 100), 2)

            for pos in positions:
                if account.exchange == Exchange.BINANCE and isinstance(pos, BinancePosition):
                    inst_info = pub_service.instrument_info(pos.instrument, account_type=pos.account_type)
                else:
                    inst_info = pub_service.instrument_info(pos.instrument,)
                if not inst_info or inst_info.base_currency not in curr_position_map or inst_info.margin_currency not in SUPPORT_MARGIN_CURRENCIES:
                    continue
                if inst_info.base_currency not in curr_position_map:
                    continue

                curr_position = curr_position_map[inst_info.base_currency]

                size = pos.size * (1 if pos.direction == 'buy' else -1)
                if inst_info.kind == ContratType.Future and inst_info.base_currency != inst_info.counter_currency:
                    mark_price = pub_service.mark_price(pos.instrument, kind=ContratType.Future)
                    size = size * inst_info.contract_multi / mark_price

                if pos.delta is not None:
                    curr_position['delta'] += pos.delta
                else:
                    curr_position['delta'] += size

                if inst_info.kind == ContratType.Future and inst_info.expire_timestamp and inst_info.expire_timestamp - now_ts < 86400000 * 5:
                    account_data['expire'] += 1

                if inst_info.kind == ContratType.Future:
                    curr_position['details'].append({
                        'instrument': pos.instrument,
                        'size': round(size, 4),
                        'account_type': pos.account_type,
                    })

            for open_order in open_orders:
                if account.exchange == Exchange.BINANCE and isinstance(open_order, BinanceOrder):
                    inst_info = pub_service.instrument_info(open_order.instrument, account_type=open_order.account_type)
                else:
                    inst_info = pub_service.instrument_info(open_order.instrument,)

                if not inst_info or inst_info.base_currency not in SUPPORT_BASE_CURRENCIES or inst_info.margin_currency not in SUPPORT_MARGIN_CURRENCIES:
                    self._logger.info(f'can not {open_order} inst info')
                    continue

                amount, filled_amount = open_order.amount, open_order.filled_amount
                if inst_info.reversed:
                    contract_multi = self.get_contract_multi(account.exchange, inst_info.base_currency)
                    amount = round(amount * inst_info.contract_multi / open_order.price, 4)
                    filled_amount = round(filled_amount * contract_multi / open_order.avg_price, 4) if filled_amount else 0

                account_data['open'] += 1

                account_data['open_orders'].append({
                    'currency': inst_info.base_currency,
                    'kind': inst_info.kind,
                    'instrument': open_order.instrument,
                    'order_id': open_order.order_id,
                    'price': open_order.price,
                    'amount': amount,
                    'account_type': open_order.account_type,
                    'filled_amount': filled_amount
                })
            account_data['open'] = len(account_data['open_orders'])

            for curr in SUPPORT_BASE_CURRENCIES:
                if curr in curr_position_map:
                    account_data['positions'].append(curr_position_map[curr])

            for position_data in account_data['positions']:
                position_data['spot'] = round(position_data['spot'], 4)
                position_data['delta'] = round(position_data['delta'], 4)
            self._logger.info(f'{account.id} query account summary token {time.time() - s}')
            return account_data
        except Exception as e:
            self._logger.exception(f'query binance account summary for {account.id} except: {e}')
            raise e


